接上一篇 ,这一篇我们写GUI。
上一篇提出了图书仓库的概念,更具体的想法是:这个仓库是一个文件夹,所有打开的书都往这个文件夹中复制一份,同时,我们在仓库中有一个library.json,存放书籍清单,每次打开一本书,也在该清单中记录一份,根据清单刷新我们的Library(dockwidget目录)
目前目录结构如下:
这里只是写GUI,所以不做过多的讲解,画GUI也真的没有什么好讲的。当然,这里画GUI用的是比较繁琐的方式,用Qt creator画出界面再用pyuic4生出py文件会比较方便一点,讲真,中文的PyQt的资料实在太少了,有空的话可以写一个中文教程(好像又给自己挖坑了)。这里我就直接贴代码了。
项目中总会有一些常量,我们把它记录在constants.py中,同时这个模块进行初始化的操作,新建必要的文件夹,数据文件。
1 2 3 4 5 6 7 8 9 10 11 12 import os PROJECT_DIR = os.path.abspath(os.path.dirname(__file__)) LIBRARY_DIR = os.path.join(PROJECT_DIR, 'bookdata') + os.sep if not os.path.exists(LIBRARY_DIR): os.mkdir(LIBRARY_DIR) LIBRARY = os.path.join(LIBRARY_DIR, "library.json") if not os.path.exists(LIBRARY): open(LIBRARY, 'w').close()
由于目录结构的变化,上一篇写的books.py也有一点变化,LIBRARY_DIR可以从constants模块中导入,还要加上两行
parentdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, parentdir)
这样就能从父模块中导入constants,所以books.py就变成了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 #!/usr/bin/env python # -*- coding: utf-8 -*- import os import zipfile import sys from lxml import etree from BeautifulSoup import BeautifulStoneSoup parentdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, parentdir) from constants import LIBRARY_DIR # LIBRARY_DIR = os.path.abspath('.') + os.sep RECOVER_PARSER = etree.XMLParser(recover=True, no_network=True) NAMESPACES = { 'dc': 'http://purl.org/dc/elements/1.1/', } class Book(object): u""" 需要主动调用open方法才能获得相应的属性 """ _FILE = LIBRARY_DIR + '%s.epub' def __init__(self, book_id=None): if book_id: self.open(book_id) def fromstring(self, raw, parser=RECOVER_PARSER): return etree.fromstring(raw, parser=parser) def read_doc_props(self, raw): u""" :param raw: raw string of xml :return: """ root = self.fromstring(raw) self.title = root.xpath('//dc:title', namespaces={'dc': NAMESPACES['dc']})[0].text self.author = root.xpath('//dc:creator', namespaces={'dc': NAMESPACES['dc']})[0].text def open(self, book_id=None): if book_id: self.book_id = book_id if not self.book_id: raise Exception('Book id not set') self.f = zipfile.ZipFile(self._FILE % self.book_id, 'r') soup = BeautifulStoneSoup(self.f.read('META-INF/container.xml')) oebps = soup.findAll('rootfile')[0]['full-path'] folder = oebps.rfind(os.sep) self.oebps_folder = '' if folder == -1 else oebps[:folder+1] # 找到oebps的文件夹名称 oebps_content = self.f.read(oebps) self.read_doc_props(oebps_content) opf_bs = BeautifulStoneSoup(oebps_content) ncx = opf_bs.findAll('item', {'id': 'ncx'})[0] ncx = self.oebps_folder + ncx['href'] # 找到ncx的完整路径 ncx_bs = BeautifulStoneSoup(self.f.read(ncx)) self.chapters = [(nav.navlabel.text, nav.content['src']) for nav in ncx_bs.findAll('navmap')[0].findAll('navpoint')] if __name__ == '__main__': book = Book('莎士比亚全集') print book.oebps_folder print book.title print book.author print str(book.chapters).decode("unicode-escape").encode("utf-8")
接下来,是bookview.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #!/usr/bin/env python # -*- coding: utf-8 -*- from PyQt4.QtGui import (QWidget, QPushButton, QHBoxLayout, QVBoxLayout, QListWidget, QLabel, QSplitter) from PyQt4.QtWebKit import QWebView class BookView(QSplitter): def __init__(self, parent=None): super(BookView, self).__init__(parent=parent) self.create_layout() def create_layout(self): self.web_view = QWebView() self.chapter_list = QListWidget() self.next_button = QPushButton("Next chapter") self.previous_button = QPushButton("Previous chapter") hbox = QHBoxLayout() hbox.addStretch() hbox.addWidget(self.previous_button) hbox.addWidget(self.next_button) vbox = QVBoxLayout() vbox.addWidget(QLabel("Chapters")) vbox.addWidget(self.chapter_list) vbox.addLayout(hbox) widget = QWidget() widget.setLayout(vbox) self.addWidget(self.web_view) self.addWidget(widget)
library.py :1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 #!/usr/bin/env python # -*- coding: utf-8 -*- import json import os import sys from PyQt4.QtGui import QTableWidget, QTableWidgetItem from PyQt4.QtCore import Qt parentdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, parentdir) from constants import LIBRARY def get_library(): with open(LIBRARY, 'r') as f: try: library = json.load(f) except Exception, e: print(e) library = {'books': []} return library def insert_library(book): u""" :param book: books.py中定义的类型, 有id, 有title, 有authors :return: """ lib = get_library() book.open() lib['books'].append({'id': book.book_id, 'title': book.title, 'author': book.author}) with open(LIBRARY, 'w') as f: json.dump(lib, f, indent=4) # 下面的GUI代码不应该跟逻辑代码写在一起,这里的写法不是好例子 class LibraryTableWidget(QTableWidget): def __init__(self, book_view, parent=None): super(LibraryTableWidget, self).__init__(parent=None) self.book_view = book_view self.setColumnCount(2) self.refresh() def refresh(self): self.library = get_library() self.clear() self.setRowCount(len(self.library['books'])) self.setHorizontalHeaderLabels(['Title', 'Authors']) for i, book in enumerate(self.library['books']): for j, cell in enumerate((book['title'], book['author'])): item = QTableWidgetItem(cell) item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsEnabled) self.setItem(i, j, item) self.resizeColumnsToContents() def create_connections(self): pass def view_book(self): book_id = self.library['books'][self.currentRow()]['id'] self.book_view.load_book(book_id)
window.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 #!/usr/bin/env python # -*- coding: utf-8 -*- import os import sys import shutil from PyQt4.QtCore import Qt, SIGNAL, SLOT from PyQt4.QtGui import (QMainWindow, QDockWidget, QAction, QApplication, QMessageBox, QFileDialog) from library import LibraryTableWidget, insert_library from bookview import BookView from books import Book parentdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, parentdir) from constants import LIBRARY_DIR class MainWindow(QMainWindow): def __init__(self): super(MainWindow, self).__init__() self.create_layout() self.create_actions() self.create_menus() self.create_connections() def create_layout(self): self.book = BookView(self) self.setCentralWidget(self.book) self.create_library_dock() def create_library_dock(self): if getattr(self, 'dock', None): self.dock.show() return self.dock = QDockWidget("Library", self) self.dock.setAllowedAreas(Qt.LeftDockWidgetArea|Qt.RightDockWidgetArea) self.library = LibraryTableWidget(self.book) self.dock.setWidget(self.library) self.addDockWidget(Qt.LeftDockWidgetArea, self.dock) def create_menus(self): file_menu = self.menuBar().addMenu("&File") help_menu = self.menuBar().addMenu("&Help") file_menu.addAction(self.library_action) file_menu.addAction(self.open_action) file_menu.addSeparator() file_menu.addAction(self.quit_action) help_menu.addAction(self.help_action) help_menu.addAction(self.about_action) def create_actions(self): self.library_action = QAction("&Library", self) self.open_action = QAction("&Open", self) self.quit_action = QAction("&Quit", self) self.help_action = QAction("Help", self) self.about_action = QAction("&About", self) def create_connections(self): self.connect(self.library_action, SIGNAL("triggered()"), self.create_library_dock) self.connect(self.open_action, SIGNAL("triggered()"), self.open_book) self.connect(self.quit_action, SIGNAL("triggered()"), QApplication.instance(), SLOT("closeAllWindows")) self.connect(self.about_action, SIGNAL("triggered()"), self.about) self.connect(self.help_action, SIGNAL("triggered()"), self.help) def about(self): QMessageBox.about(self, "QtBooks", "An ebook reader") def help(self): QMessageBox.information(self, 'Help', 'Nothing yet!') def open_book(self): book_path = QFileDialog.getOpenFileName(self, u'打开Epub格式电子书', ".", "(*.epub)") print u"in open_book, book_name is:" + str(book_path) print u"in open_book, bookdata path:" + str(LIBRARY_DIR) print os.path.dirname(str(book_path)) if os.path.dirname(str(book_path))+os.sep != str(LIBRARY_DIR): shutil.copy(str(book_path), LIBRARY_DIR) file_name = os.path.basename(str(book_path)) book_id = file_name.split('.epub')[0] book = Book(book_id) insert_library(book) self.library.refresh()
最后是main.py:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 # -*- coding: utf-8 -*- #!/usr/bin/env python import sys from PyQt4.QtGui import QApplication from src.window import MainWindow reload(sys) sys.setdefaultencoding('utf8') def main(): app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec_()) if __name__ == '__main__': main()
MainWindow的部分除了GUI,还加上了几个无关紧要的弹出对话框的内容,涉及到Qt的信号槽机制,这部分留到下一篇。
只加上了一段逻辑代码,可以打开epub文件,并将该文件复制到仓库中(文件系统中), 同时刷新LibraryTableWidget的内容,使得书名,作者显示出来: